package com.sweet.jpa.repository;

import com.sweet.jpa.domain.Sort;
import com.sweet.jpa.query.QueryUtil;
import com.sweet.lang.ArrayUtil;
import com.sweet.page.Page;
import com.sweet.page.Pageable;
import com.sweet.util.Assert;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 通用持久层实现
 *
 * @author limi 2018-03-16 18:04
 */
@Transactional(
        readOnly = true
)
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
    private Class<T> entityClass;   //实体类类型
    private String entityName;      //实体类名称
    private final EntityManager entityManager; //实体管理器
    private final JpaEntityInformation<T, ?> entityInformation;

    public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);

        Assert.notNull(entityInformation, "JpaEntityInformation must not be null!");
        Assert.notNull(entityManager, "EntityManager must not be null!");

        this.entityInformation = entityInformation;
        this.entityManager = entityManager;
        this.entityName = this.entityInformation.getEntityName();
    }

    public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
        this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
        this.entityClass = domainClass;
        //this.entityName =this.entityClass.getSimpleName();可以用上面的构造函数中的this.entityInformation.getEntityName()代替
    }

    //    简化版，不保留JpaEntityInformation
    //    public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
    //        super(domainClass, em);
    //        this.entityManager = em;
    //        this.entityClass = domainClass;
    //        this.entityName = this.entityClass.getSimpleName();
    //    }

    @Override
    @Transactional
    public T insert(T entity) {
        Assert.notNull(entity, "The given entity must not be null!");
        this.entityManager.persist(entity);
        return entity;
    }

    @Override
    @Transactional
    public List<T> insertAll(Iterable<T> entities) {
        Assert.notNull(entities, "The given Iterable of entities not be null!");
        ArrayList<T> result = new ArrayList();
        for (T entity : entities) {
            result.add(this.insert(entity));
        }
        return result;
    }

    @Override
    @Transactional
    public T update(T entity) {
        Assert.notNull(entity, "The given entity must not be null!");
        return this.entityManager.merge(entity);
    }

    @Override
    @Transactional
    public List<T> updateAll(Iterable<T> entities) {
        Assert.notNull(entities, "The given Iterable of entities not be null!");
        ArrayList<T> result = new ArrayList();
        for (T entity : entities) {
            result.add(this.update(entity));
        }
        return result;
    }

    // 性能太差，暂时不采用，会生成一大堆的关联查询方法
    //    @Override
    //    @Transactional
    //    public T saveOrUpdate(T entity) {
    //        Assert.notNull(entity, "The given entity must not be null!");
    //        if (this.entityInformation.isNew(entity)) {
    //            this.add(entity);
    //            System.out.println("添加->实体为新数据...");
    //        } else {
    //            this.update(entity);
    //            System.out.println("更新->实体为旧数据...");
    //        }
    //
    //        return entity;
    //    }
    //
    //    @Override
    //    @Transactional
    //    public List<T> saveOrUpdateAll(Iterable<T> entities) {
    //        Assert.notNull(entities, "The given Iterable of entities not be null!");
    //        ArrayList<T> result = new ArrayList();
    //        for (T entity : entities) {
    //            result.add(this.saveOrUpdate(entity));
    //        }
    //        return result;
    //    }

    @Override
    @Transactional
    public void removeById(ID id) {
        Assert.notNull(id, "The given id must not be null!");
        this.remove(this.getById(id));
    }

    @Override
    @Transactional
    public void remove(T entity) {
        Assert.notNull(entity, "The given entity must not be null!");
        this.entityManager.remove(entity);
    }

    @Override
    @Transactional
    public void removeAll(Iterable<T> entities) {
        Assert.notNull(entities, "The given Iterable of entities not be null!");
        for (T entity : entities) {
            this.remove(entity);
        }
    }

    @Override
    @Transactional
    public void removeAllById(Iterable<ID> ids) {
        Assert.notNull(ids, "The given Iterable of ids not be null!");
        for (ID id : ids) {
            this.removeById(id);
        }
    }

    @Override
    @Transactional
    public int clear() {
        //采用查询所有实体，逐个删除实现
        //spring-data-jpa的deleteAll()方法如下,性能不好，不采用
        //List<T> entitys = this.findAll();
        //if (entitys != null && entitys.size() > 0) {
        //    this.removeAll(entitys);
        //}

        //采用生成语句来实现
        return this.executeUpdate(QueryLanguage.JPQL, QueryUtil.getDeleteAllQueryString(this.entityName));
    }

    @Override
    public int getCount() {
        return this.getCount(QueryLanguage.JPQL, QueryUtil.getCountQueryString(this.entityName));
    }

    @Override
    public T getById(ID id) {
        Assert.notNull(id, "The given id must not be null!");
        return this.entityManager.find(this.entityClass, id);
    }

    @Override
    public List<T> getAll() {
        return this.queryList(QueryLanguage.JPQL, QueryUtil.getQueryString(this.entityName));
    }

    @Override
    public List<T> getAll(Sort sort) {
        return this.queryList(QueryLanguage.JPQL, QueryUtil.getQueryString(this.entityName, sort));
    }

    @Override
    public List<T> getAllById(Iterable<ID> ids) {
        Assert.notNull(ids, "The given Iterable of ids not be null!");
        ArrayList<T> result = new ArrayList();
        for (ID id : ids) {
            result.add(this.getById(id));
        }
        return result;
    }

    @Override
    public Page<T> getAll(Pageable pageable) {
        return this.queryPage(pageable, QueryLanguage.JPQL, QueryUtil.getQueryString(this.entityName), QueryUtil.getCountQueryString(this.entityName));
    }

    @Override
    public Page<T> getAll(Pageable pageable, Sort sort) {
        return this.queryPage(pageable, QueryLanguage.JPQL, QueryUtil.getQueryString(this.entityName, sort), QueryUtil.getCountQueryString(this.entityName));
    }

    @Override
    @Transactional
    public int executeUpdate(QueryLanguage language, String queryString, Object... values) {
        Query query = this.createQuery(language, queryString, values);
        return query == null ? 0 : query.executeUpdate();
    }

    //待测试
    @Override
    public int getCount(QueryLanguage language, String queryString, Object... values) {
        return (int) this.toLong(this.getUniqueResult(language, queryString, values));
    }

    @Override
    public List<T> getAll(QueryLanguage language, String queryString, Object... values) {
        return this.queryList(language, queryString, values);
    }

    //待测试
    @Override
    public List<Object[]> getArray(QueryLanguage language, String queryString, Object... values) {
        return this.queryList(language, queryString, values);
    }

    //待测试
    @Override
    public <O> List<O> getObject(QueryLanguage language, String queryString, Object... values) {
        return this.queryList(language, queryString, values);
    }

    @Override
    public Page<T> getAll(Pageable pageable, QueryLanguage language, String queryString, Object... values) {
        return this.queryPage(pageable, language, queryString, QueryUtil.parseCountQueryString(queryString), values);
    }

    @Override
    public Page<Object[]> getArray(Pageable pageable, QueryLanguage language, String queryString, Object... values) {
        return this.queryPage(pageable, language, queryString, QueryUtil.parseCountQueryString(queryString), values);
    }

    @Override
    public <O> Page<O> getObject(Pageable pageable, QueryLanguage language, String queryString, Object... values) {
        return this.queryPage(pageable, language, queryString, QueryUtil.parseCountQueryString(queryString), values);
    }

    @Override
    public Object getUniqueResult(QueryLanguage language, String queryString, Object... values) {
        Query query = this.createQuery(language, queryString, values);
        return query == null ? null : query.getSingleResult();
    }

    /**
     * 查询数据
     *
     * @param language    查询语言
     * @param queryString 查询语句
     * @param values      占位符值列表，从?0开始
     * @return
     */
    private List queryList(QueryLanguage language, String queryString, Object... values) {
        return this.queryList(null, language, queryString, values);
    }

    /**
     * 查询数据
     *
     * @param pageable    分页信息
     * @param language    操作语言
     * @param queryString 操作语句
     * @param values      占位符值列表，从?0开始
     * @return
     */
    private List queryList(Pageable pageable, QueryLanguage language, String queryString, Object... values) {
        Query query = this.createQuery(pageable, language, queryString, values);
        return query == null ? null : query.getResultList();
    }

    /**
     * 查询分页数据
     * 使用占位符设值,从?0开始
     *
     * @param pageable         分页信息
     * @param language         查询语言
     * @param queryListString  查询列表语句
     * @param queryCountString 查询总数语句
     * @param values           占位符值列表，从?0开始
     * @return
     */
    private Page queryPage(Pageable pageable, QueryLanguage language, String queryListString, String queryCountString, Object... values) {
        Assert.notNull(pageable, "The given pageable must not be null!");

        //定义查询总数、分页信息
        int total;
        Page page;

        //1.若当前为第一页，则直接查询列表；若返回的列表总数小于当前查询量，则直接填充total=list.size()；此方式可减少一次count查询。
        if (pageable.getNumber() == 1) {
            //查询分页数据
            List datas = this.queryList(pageable, language, queryListString, values);
            if (datas == null || datas.size() == 0) {
                total = 0;             //查询无数据，则直接填充total
            } else if (datas.size() < pageable.getSize()) {
                total = datas.size();  //查询总数小于查询量，则直接填充total
            } else {
                total = this.getCount(language, queryCountString, values);  //重新查询总数
            }

            //创建分页信息
            page = new Page(pageable, total, datas);
        } else {
            //2.若当前非第一页，则优先查询数据总数，若有总数，则分析当前查询页码是否合理，已超出正常支持的分页，则不查询列表，未超出则查询列表；此方式可减少一次list查询
            total = this.getCount(language, queryCountString, values);

            //封装分页数据
            page = new Page(pageable, total);
            if (total < 1) {
                return page;
            }

            //验证当前页码是否超出合理范围
            if (pageable.getNumber() > page.getTotalPages()) {
                return page;
            }

            //查询并填充分页数据
            page.setContent(this.queryList(pageable, language, queryListString, values));
        }

        return page;
    }


    /**
     * 创建Query
     * 使用占位符设值
     *
     * @param language    操作语言
     * @param queryString 操作语句
     * @param values      占位符值列表，从?0开始
     * @return
     */
    private Query createQuery(QueryLanguage language, String queryString, Object... values) {
        return this.createQuery(null, language, queryString, values);
    }

    /**
     * 创建Query
     * 使用占位符设值
     *
     * @param pageable    分页信息
     * @param language    操作语言
     * @param queryString 操作语句
     * @param values      占位符值列表，从?0开始
     * @return
     */
    private Query createQuery(Pageable pageable, QueryLanguage language, String queryString, Object... values) {
        //创建Query
        Query query = null;
        if (language == QueryLanguage.JPQL) {
            query = this.entityManager.createQuery(queryString);
        } else if (language == QueryLanguage.NATIVE) {
            query = this.entityManager.createNativeQuery(queryString);
        }

        // Query处理
        if (query != null) {
            //动态设值
            if (ArrayUtil.isNotEmpty(values)) {
                this.setQueryParameters(query, values);
            }

            //设置分页游标
            if (pageable != null) {
                this.setQueryCursor(pageable, query);
            }
        }

        return query;
    }

    /**
     * 设置分页游标
     *
     * @param pageable 分页参数
     * @param query    查询对象
     */
    private void setQueryCursor(Pageable pageable, Query query) {
        query.setFirstResult(pageable.getSize() * (pageable.getNumber() - 1));
        query.setMaxResults(pageable.getSize());
    }

    /**
     * 自动填充查询语句的参数值
     * 使用占位符设值,从?0开始
     *
     * @param query
     * @param values
     */
    private void setQueryParameters(Query query, Object... values) {
        int index = 0;
        for (Object value : values) {
            query.setParameter(index, value);
            index++;
        }
    }

    /**
     * 转为long
     *
     * @param result
     * @return
     */
    private long toLong(Object result) {
        return result == null ? 0 : ((Number) result).longValue();
    }
}